package com.luffy.devops

import org.yaml.snakeyaml.Yaml
import groovy.json.JsonSlurperClassic
import groovy.time.TimeCategory

def init(String resourcePath, Boolean watch = true, String workloadFilePath){
    this.resourcePath = resourcePath
    this.msg = new BuildMessage()
    this.watch = watch
    this.workloadFilePath = workloadFilePath
    if(!resourcePath && !workloadFilePath){         //检测 2个参数
        throw Exception("参数的资源路径非法：illegal resource path")
    }
    return this
}


def start(){
    try{
        this.tplHandler()
        sh "echo '执行部署...' ;kubectl apply -f ${this.resourcePath}"
    } catch (Exception exc){
        updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'failed')
        this.msg.updateBuildMessage(env.BUILD_TASKS, "${env.stage_name} failed...  x")
        throw exc
    }

    if (this.watch) {                                       // 检测开关是开，检测pod状态
        initWorkload()                                      // 初始化workload读取yaml资源文件拿到3个信息：kind资源类型，部署项目的namespace，metadata name资源名称
        String namespace = this.workloadNamespace
        String name = env.workloadName
        if(env.workloadType.toLowerCase() == "deployment"){             //对deployment资源类型进行监控
            echo "已部署....开始观察检测 deployment 状态 ${env.workloadName}..."
            monitorDeployment(namespace, name)
        }else {                                                         //对 其他 资源类型监控 todo
            //todo
            echo "workload type ${env.workloadType} does not support for now..."
        }

    }else {                                                 //检测开关是关闭，直接发送gitlab通知信息
        echo "构建和检测状态部署成功通知gitlab"
        updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
        echo "构建和检测状态部署成功通知添加钉钉消息"
        this.msg.updateBuildMessage(env.BUILD_TASKS, "${env.STAGE_NAME} OK...  √")
    }

}



def tplHandler(){
    /**
     *  多分支替换 针对多个资源清单文件 将命名空间 和 ingress域名 统一替换
     *  还需要 提前在各自ns中新建CM配置: NAMESPACE= xx 和 INGRESS_MYBLOG= xx ，只需要维护这个ConfigMap配置就可以
     */
    sh "sed -i 's#{{IMAGE_URL}}#${env.CURRENT_IMAGE}#g' ${this.resourcePath}/*"
    String namespace = "dev"                            //默认部署在命名空间 dev中

    if(env.TAG_NAME){                                   //如果env.TAG_NAME有值代表来自于master的tag的构建
        namespace = "test"                              //tag的构建默认部署在命名空间 test中
    }
    try {
        def configMapData = this.getResource(namespace, "devops-config", "configmap")["data"]
        configMapData.each { k, v ->
            echo "遍历当前ns下的ConfigMap key为： ${k}, CM的val为： ${v} "
            sh "echo '将这些值替换到多个资源清单yaml文件中...' ; sed -i 's#{{${k}}}#${v}#g' ${this.resourcePath}/*"
        }
    }catch (Exception exc) {
        echo "异常:未能获取devops-config 这个CM中数据: ${exc}."
        throw exc
    }
}







def initWorkload() {
    /**
     * 初始化Workload主要通过读取yaml文件，拿到3个信息：kind资源类型，部署项目的namespace，metadata name资源名称
     */
    try {
        def content = readFile this.workloadFilePath
        Yaml parser = new Yaml()
        def data = parser.load(content)             //解析yaml
        def kind = data["kind"]
        if (!kind) {                                //判断yaml是否有kind资源
            throw Exception("资源文件不合法，没有定义kind类型：workload file ${kind} illegal, will exit pipeline!")
        }
            env.workloadType = kind                     //拿到kind资源类型赋值给环境变量
        echo "${data}"
        this.workloadNamespace = data["metadata"]["namespace"]
        if (!this.workloadNamespace){               //如果yaml资源文件没有定义namespace，默认在default
            this.workloadNamespace = "default"          //拿到部署项目的namespace
        }
        env.workloadName = data["metadata"]["name"]     //拿到 metadata name资源名称 赋值给环境变量

    } catch (Exception exc) {
        echo "failed to readFile ${this.workloadFilePath},exception: ${exc}."
        throw exc
    }
}


def monitorDeployment(String namespace, String name, int timeoutMinutes = 5, sleepTime = 3) {
    /**
     * timeoutMinutes 总共检测时间 默认5分钟
     * sleepTime 每次检测状态间隔3秒
     */
    def readyCount = 0          //检测初始次数
    def readyTarget = 3         //连续检测3次
    use( TimeCategory ) {
        def endTime = TimeCategory.plus(new Date(), TimeCategory.getMinutes(timeoutMinutes))
        def lastRolling
        while (true) {
            if (new Date() >= endTime) {                                //检测超时
                echo "检测超时...."
                this.printContainerLogs(lastRolling)
                updateGitlabCommitStatus(name: 'deploy', state: 'failed')
                this.msg.updateBuildMessage(env.BUILD_TASKS, "${env.STAGE_NAME} Failed...  x")
                throw new Exception("deployment 检测超时...")
            }
            // 检测deployment是否正常
            try {
                def rolling = this.getResource(namespace, name, "deployment")   //拿到资源的json文件
                lastRolling = rolling
                if (this.isDeploymentReady(rolling)) {
                    readyCount++                                    //每次成功+1，如果连续3次都成功，才算真正成功，避免瞬时成功后失败
                    echo "当前检测 ${readyCount} 次 / 总 ${readyTarget} 次"
                    if (readyCount >= readyTarget) {
                        updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
                        this.msg.updateBuildMessage(env.BUILD_TASKS, "${env.STAGE_NAME} OK...  √")
                        break
                    }

                } else {                                            //有一次状态不正常，之前检测正常次数也要清0，重新开始检测
                    readyCount = 0
                    echo "pod状态非Running，已将检测次数清0重新检测: ${readyCount}"
                    this.printContainerLogs(lastRolling)
                    sh "echo 'pod非Running event日志' ; kubectl get pod -n ${namespace} -o wide"
                }
            } catch (Exception exc) {
                updateGitlabCommitStatus(name: 'deploy', state: 'failed')
                this.msg.updateBuildMessage(env.BUILD_RESULT, "${env.STAGE_NAME} Failed...  x")
                echo "error: ${exc}"
            }
            sleep(sleepTime)                                        //每次检测如果不满足所有pod都是正常，那么sleep 3秒继续检测
        }
    }
    return this
}

def getResource(String namespace = "default", String name, String kind="deployment") {
    /**
     * 拿到该部署项目的Json资源格式
     */
    sh "kubectl get ${kind} -n ${namespace} ${name} -o json > ${namespace}-${name}-yaml.yml"
    def jsonStr = readFile "${namespace}-${name}-yaml.yml"
    def jsonSlurper = new JsonSlurperClassic()
    def jsonObj = jsonSlurper.parseText(jsonStr)
    return jsonObj
}


def printContainerLogs(deployJson) {
    /**
     * 打印 pod describe详细日志
     */
    if (deployJson == null) {
        return;
    }
    def namespace = deployJson.metadata.namespace
    def name = deployJson.metadata.name
    def labels=""
    deployJson.spec.template.metadata.labels.each { k, v ->
        labels = "${labels} -l=${k}=${v}"
    }
    sh "kubectl describe pods -n ${namespace} ${labels}"
}

def isDeploymentReady(deployJson) {
    /**
     * 通过kubectl 获取json格式的状态，从json中取值检测
     */
    def status = deployJson.status
    def replicas = status.replicas
    def unavailable = status['unavailableReplicas']
    def ready = status['readyReplicas']
    if (unavailable != null) {                      //kubectl 获取json格式的状态，如果有'unavailableReplicas'代表不可用
        return false
    }
    def deployReady = (ready != null && ready == replicas)
    // get pod information
    if (deployJson.spec.template.metadata != null && deployReady) {
        if (deployJson.spec.template.metadata.labels != null) {
            def labels=""
            def namespace = deployJson.metadata.namespace
            def name = deployJson.metadata.name
            deployJson.spec.template.metadata.labels.each { k, v ->
                labels = "${labels} -l=${k}=${v}"
            }
            if (labels != "") {
                sh "kubectl get pods -n ${namespace} ${labels} -o json > ${namespace}-${name}-json.json"
                def jsonStr = readFile "${namespace}-${name}-json.json"
                def jsonSlurper = new JsonSlurperClassic()
                def jsonObj = jsonSlurper.parseText(jsonStr)
                def totalCount = 0
                def readyCount = 0
                jsonObj.items.each { k, v ->                            //循环检测状态是否是running
                    echo "pod状态 : ${k.status.phase}，循环检测pod副本健康情况... "
                    if (k.status.phase != "Terminating" && k.status.phase != "Evicted") {
                        totalCount++;
                        if (k.status.phase == "Running") {
                            readyCount++;
                        }
                    }
                }
                echo "检查结果：Pod中 副本数 == running数 ： ${totalCount} == ${readyCount}"
                return totalCount > 0 && totalCount == readyCount
            }
        }
    }
    return deployReady
}

